package com.me.myFramework.myDI.util;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 手写 Spring: 模拟 XML解析
 *
 * @author ME
 * @date   2019/12/9
 */
public class MyBeanFactory {

    private Map<String, MyBeanDefinition> beanDefinitions = new HashMap<>();
    private Map<String, Object> beans = new HashMap<>();

    public MyBeanFactory(String xmlPath) {
        super();
        parse(xmlPath);
    }

    private void parse(String xmlPath) {
        parseXml(xmlPath);
        initBeans();
    }

    /**
     * 解析 XML文件, 并转换成 MyBeanDefinition
     *
     * @param xmlPath XML文件的路径
     */
    private void parseXml(String xmlPath) {
        try {
            File file = new File(this.getClass().getResource("/").getPath() + File.separator + xmlPath);
            SAXReader reader = new SAXReader();
            Document doc = reader.read(file);
            Element root = doc.getRootElement();
            for (Iterator it = root.elementIterator(); it.hasNext(); ) {
                Element elementLv1 = (Element) it.next();
                // 解析 <bean>
                Attribute attrId = elementLv1.attribute("id");
                String beanName = attrId.getValue();
                Attribute attrClass = elementLv1.attribute("class");
                String clazz = attrClass.getValue();
                MyBeanDefinition beanDefinition = new MyBeanDefinition(beanName, clazz);

                for (Iterator<Element> it2 = elementLv1.elementIterator(); it2.hasNext(); ) {
                    // 二级子元素为: <property /> 和 <constructor-arg />
                    Element elementLv2 = it2.next();
                    String name = elementLv2.getName();
                    if (name.equals("property")) {
                        // 解析 <property name="" ref="" value="" />
                        Map<String, String> property = new HashMap<>();
                        beanDefinition.addProperty(property);

                        property.put("name", elementLv2.attribute("name").getValue());
                        parseNameAndValue(elementLv2, property);
                    } else if (name.equals("constructor-arg")) {
                        // 解析 <constructor-arg name="" index="" type="" ref="" value="" />
                        Map<String, String> constructorArg = new HashMap<>();
                        beanDefinition.addConstructorArg(constructorArg);

                        if (elementLv2.attribute("name") != null) {
                            constructorArg.put("name", elementLv2.attribute("name").getValue());
                        }
                        if (elementLv2.attribute("index") != null) {
                            constructorArg.put("index", elementLv2.attribute("index").getValue());
                        }
                        if (elementLv2.attribute("type") != null) {
                            constructorArg.put("type", elementLv2.attribute("type").getValue());
                        }
                        parseNameAndValue(elementLv2, constructorArg);

                    }

                }

                this.beanDefinitions.put(beanName, beanDefinition);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * <property /> 和 <constructor-arg /> 公共属性的处理
     *
     * @param elementLv2
     * @param property
     */
    private void parseNameAndValue(Element elementLv2, Map<String, String> property) {
        Attribute attrRef = elementLv2.attribute("ref");
        Attribute attrValue = elementLv2.attribute("value");
        if (attrRef != null && attrValue != null) {
            throw new RuntimeException("property 不允许同时存在 ref 和 value!");
        } else if (attrRef != null) {
            property.put("ref", attrRef.getValue());
        } else if (attrValue != null) {
            property.put("value", attrValue.getValue());
        }
    }

    /**
     * 解析 beanDefinition容器, 并生成对应的 bean
     */
    private void initBeans() {
        try {
            for (Map.Entry<String, MyBeanDefinition> beanDefinitionEntry : beanDefinitions.entrySet()) {
                MyBeanDefinition beanDefinition = beanDefinitionEntry.getValue();
                initBean(beanDefinition);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(beans);
        System.out.println();
    }

    /**
     * 根据指定的 beanDefinition初始化 bean
     *
     * @param beanDefinition bean的定义信息
     */
    private void initBean(MyBeanDefinition beanDefinition) {
        try {
            String beanName = beanDefinition.getBeanName();
            String className = beanDefinition.getClassName();

            Class<?> classObj = Class.forName(className);
            Object obj = null;

            // 获得 property的值
            if (beanDefinition.getConstructorArgs().size() > 0) {
                // TODO 构造注入未实现: 根据 name/ type 注入, 均未设置或者设置了 index 最终还是根据 type注入

                // 根据 type注入, name/ type/ index 均未设置时, 也可以通过 ref.getClass() 获得 type
                // 暂存构造函数的参数列表和参数类型列表
                List<Object> argList = new ArrayList<>();
                List<Class> classList = new ArrayList<>();
                List<Map<String, String>> constructorArgs = beanDefinition.getConstructorArgs();
                for (Map<String, String> arg : constructorArgs) {
                    // TODO 存在构造参数排序问题, 反过来试试? 不考虑 index
                    String index = arg.get("index");
                    // TODO 假设是单接口
                    Object refObj = this.getBean(arg.get("ref"));
                    Class<?> intf = refObj.getClass().getInterfaces()[0];
                    argList.add(refObj);
                    classList.add(intf);
                }

                try {
                    Constructor<?> constructor = classObj.getConstructor(classList.toArray(new Class[classList.size()]));
                    // System.out.println("getParameterTypes: 参数类型");
                    // System.out.println("getGenericParameterTypes: 参数类型, 泛型特殊表示");
                    // System.out.println();

                    obj = constructor.newInstance(argList.toArray());
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("对象 [" + className + "] 中找不到指定的构造方法: ", e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("对象 [" + className + "] 的构造方法调用失败: ", e);
                }
            } else if (beanDefinition.getProperties().size() > 0) {
                obj = Class.forName(className).newInstance();
                List<Map<String, String>> properties = beanDefinition.getProperties();
                for (Map<String, String> property : properties) {
                    // 得到对应的 field
                    String fieldName = property.get("name");
                    String methodName = "";
                    try {
                        // 反射获得属性: 包括公开的和私有的
                        Field field = classObj.getDeclaredField(fieldName);
                        if (field == null) {
                            throw new RuntimeException(beanName + "找不到指定的属性: " + fieldName);
                        }

                        // 得到需要注入的 bean
                        // TODO 从容器中获取 bean, 对于尚未初始化的 bean会提前初始化
                        Object refObj = this.getBean(property.get("ref"));

                        // 方案一: 普通方法调用, 先根据 fieldName得到 setter(), 然后配合 field.getType() 获得 method
                        methodName = fieldName2MethodName(fieldName);
                        Method method = classObj.getMethod(methodName, field.getType());
                        method.invoke(obj, refObj);

                        // 方案二: 把私有的 field访问权限设置为 true, 然后调用 Field.set()
                        // field.setAccessible(Boolean.TRUE);
                        // field.set(obj, injectObj);
                    } catch (NoSuchFieldException e) {
                        throw new RuntimeException("对象 [" + className + "] 中找不到指定的属性: " + fieldName, e);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("对象 [" + className + "] 中找不到指定的方法: " + methodName, e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException("对象 [" + className + "] 的方法调用失败: " + fieldName, e);
                    }
                }
            } else {
                // 没有 <property /> 和 <constructor-arg />
                obj = Class.forName(className).newInstance();
            }
            this.beans.put(beanName, obj);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据属性名获得对应的 setter()
     *
     * @param fieldName
     * @return
     */
    private String fieldName2MethodName(String fieldName) {
        char[] chars = fieldName.toCharArray();
        if (chars[0] >= 'a' && chars[0] <= 'z') {
            chars[0] = (char)(chars[0] - 32);
        }

        return "set" + new String(chars);
    }

    /**
     * TODO 存在循环依赖的问题
     *
     * 根据 beanName从 bean容器中获取指定的 bean: <blockquote>
     *      1. Bean已经初始化, 则直接获取<p>
     *      2. Bean尚未初始化, 根据 beanName从 MyBeanDefinition容器中获取 Bean定义然后主动 initBean()</blockquote>
     *
     * @param beanName 唯一的 bean名称
     * @param <T>
     * @return
     */
    public <T> T getBean(String beanName) {
        Object obj = beans.get(beanName);
        if (obj == null) {
            MyBeanDefinition beanDefinition = beanDefinitions.get(beanName);
            if (beanDefinition == null) {
                throw new RuntimeException("未定义的 Bean: " + beanName);
            }
            try {
                initBean(beanDefinition);
            } catch (Exception e) {
                throw new RuntimeException("初始化 Bean失败: " + beanName);
            }
        }
        obj = beans.get(beanName);

        return (T) obj;
    }
}
